﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Derbster.Domain;

namespace Derbster.Data
{
    /// <summary>
    /// This is an in-memory version of repository. Note, we don't use the singleton pattern here. 
    /// Instead, we count on Unity to control the lifecycle of these objects. By default, we use
    /// a container-controlled lifecycle, so only one instance of a repository is returned per container.
    /// </summary>
    public class DbMatchRepository : IMatchRepository, IDisposable
       
    {
        private readonly DerbsterDbContext _dbContext;

        public DbMatchRepository()
        {
            _dbContext = new DerbsterDbContext();
        }

        public DbMatchRepository(string nameOrConnectionString)
        {
            _dbContext = new DerbsterDbContext(nameOrConnectionString);
        }

        public DbMatchRepository(DbConnection existingConnection, bool contextOwnsConnection)
        {
            _dbContext = new DerbsterDbContext(existingConnection, contextOwnsConnection);
        }
        public Match Create(bool isRegulation, bool isSanctioned, Roster firstTeamRoster, Roster secondTeamRoster, string name)
        {
            
            Match newMatch = new Match(isRegulation, isSanctioned, firstTeamRoster, secondTeamRoster, name);
            _dbContext.Matches.Add(newMatch);
            _dbContext.SaveChanges();
            return newMatch;
        }

        public Match Create(
            bool isRegulation,
            bool isSanctioned,
            Roster firstTeamRoster,
            Roster secondTeamRoster,
            string name,
            string location)
        {
            Match newMatch = new Match(isRegulation, isSanctioned, firstTeamRoster, secondTeamRoster, name, location);
            _dbContext.Matches.Add(newMatch);
            _dbContext.SaveChanges();
            return newMatch;
        }

        public Match Create(
            bool isRegulation,
            bool isSanctioned,
            Roster firstTeamRoster,
            Roster secondTeamRoster,
            string name,
            string location,
            DateTime? when)
        {
            Match newMatch = new Match(isRegulation, isSanctioned, firstTeamRoster, secondTeamRoster, name, location, when);
            _dbContext.Matches.Add(newMatch);
            _dbContext.SaveChanges();
            return newMatch;
        }

        public Match FindById(int id)
        {
            return _dbContext.Matches.SingleOrDefault(m => m.MatchId == id);
        }

        public Match FindByGlobalIdentifier(Guid globalGuid)
        {
            return _dbContext.Matches.SingleOrDefault(m => m.GlobalIdentifier == globalGuid);
        }

        public Match FindByName(string name)
        {
            return _dbContext.Matches.SingleOrDefault(m => m.Name.ToLower() == name.ToLower());
        }

        public IEnumerable<Match> FindByDate(DateTime fromDate, DateTime toDate)
        {
            return _dbContext.Matches.Where(m => m.When.HasValue && m.When.Value >= fromDate && m.When.Value <= toDate).AsEnumerable();
        }

        public IEnumerable<Match> FindByLocation(string location)
        {
            return _dbContext.Matches.Where(m => !String.IsNullOrWhiteSpace(m.Location) && m.Location.ToLower() == location.ToLower()).AsEnumerable();
        }

        public Match Update(Match match)
        {
            RemoveOrphanedEntities();
            _dbContext.SaveChanges();
            return match;
        }

        public void Delete(Match match)
        {
            _dbContext.Rosters.Remove(match.FirstTeamRoster);
            _dbContext.Rosters.Remove(match.SecondTeamRoster);
            if (match.OfficialRoster != null)
            {
                match.OfficialRoster.NonSkatingOfficials.ToList().ForEach(ro => _dbContext.RosteredOfficials.Remove(ro));
                match.OfficialRoster.SkatingOfficials.ToList().ForEach(ro => _dbContext.RosteredOfficials.Remove(ro));
            }
            _dbContext.Matches.Remove(match);
            _dbContext.SaveChanges();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing && _dbContext != null)
                _dbContext.Dispose();
        }

        public IEnumerator<Match> GetEnumerator()
        {
            return _dbContext.Matches.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public Expression Expression
        {
            get
            {
                return _dbContext.Matches.Expression;
            }
        }

        public Type ElementType
        {
            get
            {
                return _dbContext.Matches.ElementType;
            }
        }

        public IQueryProvider Provider
        {
            get
            {
                return _dbContext.Matches.Provider;
            }
        }

        private void RemoveOrphanedEntities()
        {
            Contract.Requires(_dbContext != null);
            Contract.Requires(_dbContext.BoxEvents != null);
            Contract.Requires(_dbContext.BoxEvents.Local != null);
            Contract.Requires(_dbContext.Jams != null);
            Contract.Requires(_dbContext.Jams.Local != null);
            Contract.Requires(_dbContext.Penalties != null);
            Contract.Requires(_dbContext.Penalties.Local != null);
            Contract.Requires(_dbContext.ScoringPasses != null);
            Contract.Requires(_dbContext.ScoringPasses.Local != null);

            // Remove orphaned box events
            _dbContext.BoxEvents.Local.Where(be=>be.Match == null)
                .ToList()
                .ForEach(be=>_dbContext.BoxEvents.Remove(be));

                // Remove orphaned jams. 
            var orphanedJams = 
                _dbContext.Jams.Local.Where(j => j.Match == null).ToList();

            foreach (var orphanedJam in orphanedJams)
            {
                _dbContext.Lineups.Remove(orphanedJam.FirstTeamLineup);
                _dbContext.Lineups.Remove(orphanedJam.SecondTeamLineup);
                _dbContext.Scoring.Remove(orphanedJam.FirstTeamScoring);
                _dbContext.Scoring.Remove(orphanedJam.SecondTeamScoring);
                _dbContext.Jams.Remove(orphanedJam);
            }

            // Remove orphaned penalties.
            _dbContext.Penalties.Local.Where(p => p.Match == null)
                .ToList()
                .ForEach(p => _dbContext.Penalties.Remove(p));
            
            // Remove orphaned passes. 
            _dbContext.ScoringPasses.Local.Where(sp => sp.Scoring == null)
                .ToList()
                .ForEach(sp => _dbContext.ScoringPasses.Remove(sp));
        }
    }
}
